<?php
	/**
	 * Class MemcacheQueue
	 * Class PHP Memcache Queue, inspiring from http://abhinavsingh.com/blog/2010/02/memq-fast-queue-implementation-using-memcached-and-php-only/.
	 */
	class MemcacheQueue {
		private $data;
		private $sleepTime;
		
		private $access;
		private $head;
		private $tail;
		
		const KEY_LOCK = 1;
		
		private $memObj;
		
		public function __construct($data = 'data', $sleepTime = 5) {
			$this->data = trim($data);
			$this->sleepTime = $sleepTime;
			$this->access = false;
		}
		
		public function addQueue($data) {
			$this->getLock();
			$result = $this->memObj->get(array(
				$this->getHead(),
				$this->getTail()
			));
			
			if ($result === false) {
				// ...add log for show error.
				return false;
			}			
			
			// If not exist, initialiaze first.
			if ((!array_key_exists($this->getHead(), $result)) || (!array_key_exists($this->getTail(), $result))) {
				$this->setHead();
				$this->setTail();			
			} else {
				$this->head = $result[$this->getHead()];
				$this->tail = $result[$this->getTail()];
			}
			
			// Add data.
			$position = $this->tail;
			$key = $this->data . '_' . $position;
			$this->memObj->add($key, $data);
			
			// Increment tail.
			$this->setTail(++$position);
			
			return $key;
		}
		
		public function getQueue() {
			$this->getLock();
			$result = $this->memObj->get(array(
				$this->getHead(),
				$this->getTail()
			));			
			
			if ($result === false) {
				// ...add log for show error.
				return null;
			}
			
			// If not exist, return null.
			if ((!array_key_exists($this->getHead(), $result)) || (!array_key_exists($this->getTail(), $result))) {
				// ...add log for show error.
				return null;
			} else {
				$this->head = $result[$this->getHead()];
				$this->tail = $result[$this->getTail()];			
			}
				
			$value = $this->memObj->get($this->getCurrentData());
			
			// Increment head.
			if ($value !== false) {
				// But delete first!
				$this->memObj->delete($this->getCurrentData());
				// Okay, increment position.
				$position = $this->head;
				$this->setHead(++$position);
			}
							
			// Check if we could reset the queue.
			if (($this->head == $this->tail) && ($this->head > 1)) {
				$this->setHead();
				$this->setTail();
			}
			
			return (($value === false) ? null : $value);
		}
		
		private function getLock() {
			if ($this->access === false) {
				$this->memObj = new Memcache();
				$this->memObj->connect('localhost');		
				// Wait for get locking.
				while (!$this->memObj->add($this->getKeyLock(), self::KEY_LOCK)) {
					sleep($this->sleepTime);
				}
				$this->access = true;
			}
		}
		
		private function releaseLock() {
			$this->memObj->delete($this->getKeyLock());
		}
		
		private function getKeyLock() {
			return $this->data . '_access';
		}
		
		private function getHead() {
			return $this->data . '_head';
		}
		
		private function setHead($position = 1) {
			// Use replace first for undefined behaviour of multiple set.
			if ($this->memObj->replace($this->getHead(), $position) == false) {
				$this->memObj->set($this->getHead(), $position);
			}
			$this->head = $position;
		}
		
		private function getTail() {
			return $this->data . '_tail';
		}
		
		private function setTail($position = 1) {
			// Use replace first for undefined behaviour of multiple set.
			if ($this->memObj->replace($this->getTail(), $position) == false) {
				$this->memObj->set($this->getTail(), $position);
			}
			$this->tail = $position;
		}
		
		private function getCurrentData() {
			return $this->data . '_' . $this->head;
		}
						
		public function __destruct() {
			$this->releaseLock();
		}
	}
	// End of file